{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline\n",
    "from nettack import utils, GCN\n",
    "from nettack import nettack as ntk\n",
    "import numpy as np\n",
    "gpu_id = None # set this to your desired GPU ID if you want to use GPU computations (only for the GCN/surrogate training)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Load network, basic setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Selecting 1 largest connected components\n"
     ]
    }
   ],
   "source": [
    "_A_obs, _X_obs, _z_obs = utils.load_npz('data/citeseer.npz')\n",
    "_A_obs = _A_obs + _A_obs.T\n",
    "_A_obs[_A_obs > 1] = 1\n",
    "lcc = utils.largest_connected_components(_A_obs)\n",
    "\n",
    "_A_obs = _A_obs[lcc][:,lcc]\n",
    "\n",
    "assert np.abs(_A_obs - _A_obs.T).sum() == 0, \"Input graph is not symmetric\"\n",
    "assert _A_obs.max() == 1 and len(np.unique(_A_obs[_A_obs.nonzero()].A1)) == 1, \"Graph must be unweighted\"\n",
    "assert _A_obs.sum(0).A1.min() > 0, \"Graph contains singleton nodes\"\n",
    "\n",
    "_X_obs = _X_obs[lcc].astype('float32')\n",
    "_z_obs = _z_obs[lcc]\n",
    "_N = _A_obs.shape[0]\n",
    "_K = _z_obs.max()+1\n",
    "_Z_obs = np.eye(_K)[_z_obs]\n",
    "_An = utils.preprocess_graph(_A_obs)\n",
    "sizes = [16, _K]\n",
    "degrees = _A_obs.sum(0).A1\n",
    "\n",
    "seed = 15\n",
    "unlabeled_share = 0.8\n",
    "val_share = 0.1\n",
    "train_share = 1 - unlabeled_share - val_share\n",
    "np.random.seed(seed)\n",
    "\n",
    "split_train, split_val, split_unlabeled = utils.train_val_test_split_tabular(np.arange(_N),\n",
    "                                                                       train_size=train_share,\n",
    "                                                                       val_size=val_share,\n",
    "                                                                       test_size=unlabeled_share,\n",
    "                                                                       stratify=_z_obs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Choose the node to attack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "u = 0 # node to attack\n",
    "assert u in split_unlabeled"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train surrogate model (i.e. GCN without nonlinear activation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 37 iterations\n"
     ]
    }
   ],
   "source": [
    "surrogate_model = GCN.GCN(sizes, _An, _X_obs, with_relu=False, name=\"surrogate\", gpu_id=gpu_id)\n",
    "surrogate_model.train(split_train, split_val, _Z_obs)\n",
    "W1 =surrogate_model.W1.eval(session=surrogate_model.session)\n",
    "W2 =surrogate_model.W2.eval(session=surrogate_model.session)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Setup Nettack"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "nettack = ntk.Nettack(_A_obs, _X_obs, _z_obs, W1, W2, u, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "direct_attack = True\n",
    "n_influencers = 1 if direct_attack else 5\n",
    "n_perturbations = int(degrees[u]) # How many perturbations to perform. Default: Degree of the node\n",
    "perturb_features = True\n",
    "perturb_structure = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Poison the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "##### Starting attack #####\n",
      "##### Attack only using feature perturbations #####\n",
      "##### Attacking the node directly #####\n",
      "##### Performing 12 perturbations #####\n",
      "##### ...1/12 perturbations ... #####\n",
      "##### ...2/12 perturbations ... #####\n",
      "##### ...3/12 perturbations ... #####\n",
      "##### ...4/12 perturbations ... #####\n",
      "##### ...5/12 perturbations ... #####\n",
      "##### ...6/12 perturbations ... #####\n",
      "##### ...7/12 perturbations ... #####\n",
      "##### ...8/12 perturbations ... #####\n",
      "##### ...9/12 perturbations ... #####\n",
      "##### ...10/12 perturbations ... #####\n",
      "##### ...11/12 perturbations ... #####\n",
      "##### ...12/12 perturbations ... #####\n"
     ]
    }
   ],
   "source": [
    "nettack.reset()\n",
    "nettack.attack_surrogate(n_perturbations, perturb_structure=perturb_structure, perturb_features=perturb_features, direct=direct_attack, n_influencers=n_influencers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(), (), (), (), (), (), (), (), (), (), (), ()]\n"
     ]
    }
   ],
   "source": [
    "print(nettack.structure_perturbations)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(0, 215), (0, 65), (0, 1989), (0, 3605), (0, 1938), (0, 878), (0, 36), (0, 2974), (0, 1508), (0, 2191), (0, 3126), (0, 546)]\n"
     ]
    }
   ],
   "source": [
    "print(nettack.feature_perturbations)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train GCN without perturbations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "retrain_iters=5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "... 1/5 \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 40 iterations\n",
      "... 2/5 \n",
      "converged after 39 iterations\n",
      "... 3/5 \n",
      "converged after 45 iterations\n",
      "... 4/5 \n",
      "converged after 52 iterations\n",
      "... 5/5 \n",
      "converged after 34 iterations\n"
     ]
    }
   ],
   "source": [
    "classification_margins_clean = []\n",
    "class_distrs_clean = []\n",
    "gcn_before = GCN.GCN(sizes, _An, _X_obs, \"gcn_orig\", gpu_id=gpu_id)\n",
    "for _ in range(retrain_iters):\n",
    "    print(\"... {}/{} \".format(_+1, retrain_iters))\n",
    "    gcn_before.train(split_train, split_val, _Z_obs)\n",
    "    probs_before_attack = gcn_before.predictions.eval(session=gcn_before.session,feed_dict={gcn_before.node_ids: [nettack.u]})[0]\n",
    "    class_distrs_clean.append(probs_before_attack)\n",
    "    best_second_class_before = (probs_before_attack - 1000*_Z_obs[nettack.u]).argmax()\n",
    "    margin_before = probs_before_attack[_z_obs[nettack.u]] - probs_before_attack[best_second_class_before]\n",
    "    classification_margins_clean.append(margin_before)\n",
    "class_distrs_clean = np.array(class_distrs_clean)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train GCN with perturbations\n",
    "(insert your favorite node classification algorithm here)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "... 1/5 \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zuegnerd/anaconda3/envs/tf/lib/python3.6/site-packages/sklearn/metrics/classification.py:1135: UndefinedMetricWarning: F-score is ill-defined and being set to 0.0 in labels with no predicted samples.\n",
      "  'precision', 'predicted', average, warn_for)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "converged after 38 iterations\n",
      "... 2/5 \n",
      "converged after 49 iterations\n",
      "... 3/5 \n",
      "converged after 37 iterations\n",
      "... 4/5 \n",
      "converged after 42 iterations\n",
      "... 5/5 \n",
      "converged after 41 iterations\n"
     ]
    }
   ],
   "source": [
    "classification_margins_corrupted = []\n",
    "class_distrs_retrain = []\n",
    "gcn_retrain = GCN.GCN(sizes, nettack.adj_preprocessed, nettack.X_obs.tocsr(), \"gcn_retrain\", gpu_id=gpu_id)\n",
    "for _ in range(retrain_iters):\n",
    "    print(\"... {}/{} \".format(_+1, retrain_iters))\n",
    "    gcn_retrain.train(split_train, split_val, _Z_obs)\n",
    "    probs_after_attack = gcn_retrain.predictions.eval(session=gcn_retrain.session,feed_dict={gcn_retrain.node_ids: [nettack.u]})[0]\n",
    "    best_second_class_after = (probs_after_attack - 1000*_Z_obs[nettack.u]).argmax()\n",
    "    margin_after = probs_after_attack[_z_obs[nettack.u]] - probs_after_attack[best_second_class_after]\n",
    "    class_distrs_retrain.append(probs_after_attack)\n",
    "    classification_margins_corrupted.append(margin_after)\n",
    "class_distrs_retrain = np.array(class_distrs_retrain)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Visualize results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x288 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def make_xlabel(ix, correct):\n",
    "    if ix==correct:\n",
    "        return \"Class {}\\n(correct)\".format(ix)\n",
    "    return \"Class {}\".format(ix)\n",
    "\n",
    "figure = plt.figure(figsize=(12,4))\n",
    "plt.subplot(1, 2, 1)\n",
    "center_ixs_clean = []\n",
    "for ix, block in enumerate(class_distrs_clean.T):\n",
    "    x_ixs= np.arange(len(block)) + ix*(len(block)+2)\n",
    "    center_ixs_clean.append(np.mean(x_ixs))\n",
    "    color = '#555555'\n",
    "    if ix == nettack.label_u:\n",
    "        color = 'darkgreen'\n",
    "    plt.bar(x_ixs, block, color=color)\n",
    "\n",
    "ax=plt.gca()\n",
    "plt.ylim((-.05, 1.05))\n",
    "plt.ylabel(\"Predicted probability\")\n",
    "ax.set_xticks(center_ixs_clean)\n",
    "ax.set_xticklabels([make_xlabel(k, nettack.label_u) for k in range(_K)])\n",
    "ax.set_title(\"Predicted class probabilities for node {} on clean data\\n({} re-trainings)\".format(nettack.u, retrain_iters))\n",
    "\n",
    "fig = plt.subplot(1, 2, 2)\n",
    "center_ixs_retrain = []\n",
    "for ix, block in enumerate(class_distrs_retrain.T):\n",
    "    x_ixs= np.arange(len(block)) + ix*(len(block)+2)\n",
    "    center_ixs_retrain.append(np.mean(x_ixs))\n",
    "    color = '#555555'\n",
    "    if ix == nettack.label_u:\n",
    "        color = 'darkgreen'\n",
    "    plt.bar(x_ixs, block, color=color)\n",
    "\n",
    "\n",
    "ax=plt.gca()\n",
    "plt.ylim((-.05, 1.05))\n",
    "ax.set_xticks(center_ixs_retrain)\n",
    "ax.set_xticklabels([make_xlabel(k, nettack.label_u) for k in range(_K)])\n",
    "ax.set_title(\"Predicted class probabilities for node {} after {} perturbations\\n({} re-trainings)\".format(nettack.u, n_perturbations, retrain_iters))\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:tf]",
   "language": "python",
   "name": "conda-env-tf-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
